// // Copyright (c) 2009 All Right Reserved // // vl // // 2009-01-01 // Contains ... using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Text; using System.Xml.Serialization; using LargoCommon.Abstract; namespace LargoCommon.Music { /// Harmonic system. /// Harmonic system is subclass of binary GSystem. It is defined by its Order, that represents number /// of tones in one octave. In addition to that, array of symbols (c,c#,d,..) /// and array of (formal) intervals (0,1,2,..) are created for given Order. [Serializable] [XmlRoot] public sealed class HarmonicSystem : GeneralSystem { #region Fields /// /// Naming base 24. /// public const byte NamingBase24 = 24; /// /// Naming base 48. /// public const byte NamingBase48 = 48; /// Maximal Continuity Quantum (3). public const float C1 = 6.0f; /// Continuity Quantum (5). public const float C2 = 3.0f; /// Continuity Quantum (7). public const float C3 = 1.0f; /// Maximal Impulse Quantum (halftone). public const float I1 = 12.0f; /// Consonance quotient. public const float ConsonanceRatio = 4.0f; /// Constant determining size of interval store. This optimization is switched off now. public const int MaximumLengthOfStoredRealInterval = 0; /// Used Systems. private static readonly Dictionary UsedSystems = new Dictionary(); /// Consonance quotient. private readonly Dictionary intervalRatios; /// Musical sharp symbols. private readonly string[] sharpSymbols; /// Musical sharp symbols. private readonly string[] flatSymbols; /// List of intervals. private Collection pitches; /// List of intervals. private Collection intervals; //// Shortcuts of harmonic structures. //// private Dictionary harStructShortcuts; //// Shortcuts of harmonic structures. //// private Dictionary harStructShortcutsMoot; #endregion #region Constructors /// Initializes a new instance of the HarmonicSystem class. Serializable. public HarmonicSystem() { } /// Initializes a new instance of the HarmonicSystem class. /// Order of system. public HarmonicSystem(byte order) : base(2, order) { if (order == 0) { throw new ArgumentException("Order of system must not be 0."); } this.intervalRatios = new Dictionary(); this.sharpSymbols = this.MakeSymbolArray(true); this.flatSymbols = this.MakeSymbolArray(false); this.StringOfSharpSymbols = SymbolsToString(this.sharpSymbols); this.StringOfFlatSymbols = SymbolsToString(this.flatSymbols); this.MakePitches(); this.MakeIntervals(); } /// Initializes a new instance of the HarmonicSystem class. /// Degree of system. /// Order of system. public HarmonicSystem(byte degree, byte order) : base(degree, order) { } #endregion #region Properties /// Gets list of pitches. /// Property description. [XmlIgnore] public Collection Pitches { get { Contract.Ensures(Contract.Result>() != null); if (this.pitches == null) { throw new InvalidOperationException("List of pitches is null."); } return this.pitches; } } /// Gets list of intervals. /// Property description. [XmlIgnore] public Collection Intervals { get { Contract.Ensures(Contract.Result>() != null); if (this.intervals == null) { throw new InvalidOperationException("List of intervals is null."); } return this.intervals; } } /// Gets or sets string of musical symbols. /// Property description. [XmlIgnore] public string StringOfSharpSymbols { get; set; } /// Gets or sets string of musical symbols. /// Property description. [XmlIgnore] public string StringOfFlatSymbols { get; set; } /// /// Gets the chromatic modality. /// /// Property description. public HarmonicModality ChromaticModality { get { //// long chromatic = musicalBar.FileModel.MusicalBlock.HarmonicSystem.LongSize() - 1; // (long)(Math.Pow(2, this.Order) - 1); var order = this.Order; var chromaticCode = "0"; //// new byte[order + 2]; // chromaticCode[0] = 0; chromaticCode[1] = (byte)'#'; for (var i = 0; i < order; i++) { chromaticCode += ",1"; } var modality = HarmonicModality.GetNewHarmonicModality(this, chromaticCode.ToString(CultureInfo.CurrentCulture)); return modality; } } #endregion #region Static methods /// /// Gets Harmonic System. /// /// System order. /// Returns value. public static HarmonicSystem GetHarmonicSystem(byte order) { Contract.Ensures(Contract.Result() != null); var hm = UsedSystems.ContainsKey(order) ? UsedSystems[order] : null; if (hm != null) { return hm; } hm = new HarmonicSystem(order); UsedSystems[order] = hm; return hm; } /// /// Formal measure of dissonance, higher for consonant structures. /// /// Harmonic Continuity. /// Harmonic Impulse. /// Returns value. public static float Consonance(float continuity, float impulse) { // float formalConsonance = aFContinuity-HarmonicSystem.ciSonanceRatio*aFImpulse; // float num = formalConsonance+HarmonicSystem.ciSonanceRatio*HarmonicSystem.i1; // float denominator = aFContinuity+HarmonicSystem.ciSonanceRatio*HarmonicSystem.i1; // formalConsonance = num/denominator*100f; // normalized to 0..100 var formalConsonance = (Math.Abs(continuity) + (ConsonanceRatio * (100 - impulse))) / (ConsonanceRatio + 1); return formalConsonance; } #endregion #region Public methods /// Returns ratio of the interval. /// Real system length. /// Returns value. public float RatioForInterval(int sysLength) { float ratio; if (sysLength == 0) { return 1.0f; } if (sysLength == this.Order) { return 2.0f; } if (sysLength == -this.Order) { return DefaultValue.HalfUnit; } if (this.intervalRatios.ContainsKey(sysLength)) { ratio = this.intervalRatios[sysLength]; } else { ratio = (float)Math.Pow(2.0, ((float)sysLength) / this.Order); this.intervalRatios[sysLength] = ratio; } return ratio; } /// /// Gets musical pitch. /// /// System altitude. /// Returns value. public MusicalPitch GetPitch(int systemAltitude) { if (systemAltitude < 0) { return null; } return systemAltitude < this.Pitches.Count ? this.Pitches[systemAltitude] : null; } #endregion #region MIDI support /// Returns interval Size in halftones, because of MIDI. /// Real system length. /// Returns value. public float HalftonesForInterval(int sysLength) { if (this.Order == DefaultValue.HarmonicOrder) { return sysLength; } var ratio = this.RatioForInterval(sysLength); var r1 = (float)Math.Pow(2.0, 1.0 / DefaultValue.HarmonicOrder); var lgr1 = (float)Math.Log(r1); var halftones = lgr1 >= DefaultValue.AfterZero ? (float)Math.Round(Math.Log(ratio) / lgr1, 4) : 0; return halftones; } #endregion #region Intervals /// Returns tone index of the given interval. /// Number of tone symbols. /// Formal system length. /// Returns value. public short GuessToneIndex(byte baseNumber, byte formalLength) { const float afterZero = 0.0001f; Contract.Requires(baseNumber > 0); var ratio = this.RatioForInterval(formalLength); var discreteRatio = (float)Math.Pow(2.0, DefaultValue.HalfUnit / baseNumber); var limit = discreteRatio + afterZero; for (byte i = 0; i < baseNumber; i++) { discreteRatio = (float)Math.Pow(2.0, ((float)i) / baseNumber); if (MathSupport.EqualNumbersRational(ratio, discreteRatio, limit)) { return i; } } return -1; } /// Returns name for the given interval. /// Formal system length. /// Returns value. public string GuessNameForInterval(byte formalLength) { string[] name24 = { "unison", "dim.second", "min.second", "mid.second", "maj.second", "aug.second", "min.third", "mid.third", "maj.third", "dim.fourth", "fourth", "aug.fourth", "triton", "dim.fifth", "fifth", "aug.fifth", "min.sixth", "mid.sixth", "maj.sixth", "dim.seventh", "min.seventh", "mid.seventh", "maj.seventh", "aug.seventh" }; var toneIndex = this.GuessToneIndex(NamingBase24, formalLength); var value = toneIndex >= 0 && toneIndex < name24.Length ? name24[toneIndex] : "?" + formalLength.ToString(CultureInfo.CurrentCulture.NumberFormat); return value; } #endregion #region Substructures /// /// Modality Classes. /// /// General Qualifier. /// Upper limit. /// Returns value. public Collection ModalityClasses(GeneralQualifier genQualifier, int limit) { var hv = StructuralVarietyFactory.NewHarmonicModalityVariety( StructuralVarietyType.BinaryClasses, this, genQualifier, limit); return new Collection(hv.StructList); } /// /// Modality Classes. /// /// Returns value. public Collection ModalityClasses() { var hv = StructuralVarietyFactory.NewHarmonicModalityVariety( StructuralVarietyType.BinaryClasses, this, null, 10000); return new Collection(hv.StructList); } /// /// Modality Instances. /// /// General Qualifier. /// Upper limit. /// Returns value. public Collection ModalityInstances(GeneralQualifier genQualifier, int limit) { var hv = StructuralVarietyFactory.NewHarmonicModalityVariety( StructuralVarietyType.Instances, this, genQualifier, limit); return hv.StructList; } /// /// Modality Instances. /// /// Returns value. public Collection ModalityInstances() { var hv = StructuralVarietyFactory.NewHarmonicModalityVariety( StructuralVarietyType.Instances, this, null, 10000); return hv.StructList; } /// /// Struct Classes. /// /// General Qualifier. /// Upper limit. /// Returns value. public Collection StructClasses(GeneralQualifier genQualifier, int limit) { var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety( StructuralVarietyType.BinaryClasses, this, genQualifier, limit); return hv.StructList; } /// /// Struct Classes. /// /// Returns value. public Collection StructClasses() { var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety( StructuralVarietyType.BinaryClasses, this, null, 10000); return hv.StructList; } /// /// Struct Instances. /// /// General Qualifier. /// Upper limit. /// Returns value. public Collection StructInstances(GeneralQualifier genQualifier, int limit) { var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety( StructuralVarietyType.Instances, this, genQualifier, limit); return hv.StructList; } /// /// Struct Instances. /// /// Returns value. public Collection StructInstances() { var hv = StructuralVarietyFactory.NewHarmonicStructuralVariety( StructuralVarietyType.Instances, this, null, 10000); return hv.StructList; } #endregion #region String representation /// /// Returns symbols for given element. /// /// Xml Element. /// If set to true [sharp]. /// /// Returns value. /// public string Symbol(short element, bool sharp) { var symbols = sharp ? this.sharpSymbols : this.flatSymbols; if (symbols != null && element >= 0 && element < symbols.Length) { return symbols[element]; } var formalElement = element % this.Order; if (formalElement < 0) { formalElement += this.Order; } var s = string.Empty; if (symbols != null && (formalElement >= 0 && formalElement < symbols.Length)) { return symbols[formalElement]; } s = element < 0 ? s.ToUpper(CultureInfo.CurrentCulture) : s + "'"; return s; } /// /// Determines whether the specified element is enharmonic. /// /// The element. /// /// True if the specified element is enharmonic; otherwise, false. /// public bool IsEnharmonic(short element) { var s = this.sharpSymbols[element]; return s?.Trim().Length > 1; } /// /// Returns tone symbol for the given element. /// /// Requested element. /// If set to true [sharp]. /// /// Returns value. /// public string GuessSymbolForElement(byte element, bool sharp) { var value = "?"; const char b = 'b'; //// (char)0x0185; if (this.Order <= NamingBase24) { string[] sym24Sharp = { "c", "c+", "c#", "cx", "d", "d+", "d#", "dx", "e", "e+", "f", "f+", "f#", "fx", "g", "g+", "g#", "gx", "a", "a+", "a#", "ax", "h", "h+" }; string[] sym24Flat = { "c", "c+", "d" + b, "d-", "d", "d+", "e" + b, "e-", "e", "e+", "f", "f+", "g" + b, "g-", "g", "g+", "a" + b, "a-", "a", "a+", "b", "h-", "h", "h+" }; var toneIndex = this.GuessToneIndex(NamingBase24, element); if (toneIndex >= 0 && toneIndex < NamingBase24) { value = sharp ? sym24Sharp[toneIndex] : sym24Flat[toneIndex]; } } else { string[] sym48Sharp = { "c", "c'", "c+", "c+'", "c#", "c#'", "cx", "cx'", "d", "d'", "d+", "d+'", "d#", "d#'", "dx", "dx'", "e", "e'", "e+", "e+'", "f", "f'", "f+", "f+'", "f#", "f#'", "fx", "fx'", "g", "g'", "g+", "g+'", "g#", "g#'", "gx", "gx'", "a", "a'", "a+", "a+'", "a#", "a#'", "ax", "ax'", "h", "h'", "h+", "h+'" }; var toneIndex = this.GuessToneIndex(NamingBase48, element); if (toneIndex >= 0 && toneIndex < NamingBase48) { value = sym48Sharp[toneIndex]; } } return value; } /// String representation of the object. /// Returns value. public override string ToString() { var s = new StringBuilder(); s.Append("Harmonic system\r\n"); s.Append(base.ToString() + Environment.NewLine); foreach (var hi in this.Intervals.Where(hi => hi != null)) { s.Append(hi + Environment.NewLine); } return s.ToString(); } #endregion #region Private static methods /// /// Symbols the array to string. /// /// The symbols. /// Returns value. private static string SymbolsToString(string[] symbols) { var str = new StringBuilder(); Array.ForEach( symbols, s => { str.Append(s); str.Append(","); }); return str.ToString(); } #endregion #region Private methods /// Makes array of pitch objects. private void MakePitches() { this.pitches = new Collection(); for (byte i = 0; i < 128; i++) { var mp = new MusicalPitch(this, i); this.Pitches.Add(mp); } } /// Makes array of interval objects. private void MakeIntervals() { this.intervals = new Collection(); for (byte i = 0; i < this.Order; i++) { var hI = new HarmonicInterval(this, i, 1.0f); this.Intervals.Add(hI); } } /// /// Makes array of symbols used in this GSystem. /// /// If set to true [sharp]. /// Returns value. private string[] MakeSymbolArray(bool sharp) { var str = new StringBuilder(); var symbols = new string[this.Order]; for (byte i = 0; i < this.Order; i++) { var s = this.GuessSymbolForElement(i, sharp); str.Append(s); if (i < this.Order - 1) { str.Append(","); } if (i < symbols.Length) { symbols[i] = s; } } return symbols; } #endregion } }